package communication;

import java.util.HashMap;

import publisher.Publisher;
import aimpublisher.AIMPublisher;

import com.aol.acc.AccAlert;
import com.aol.acc.AccAvManager;
import com.aol.acc.AccAvManagerProp;
import com.aol.acc.AccAvSession;
import com.aol.acc.AccAvStreamType;
import com.aol.acc.AccBartItem;
import com.aol.acc.AccBartItemProp;
import com.aol.acc.AccBuddyList;
import com.aol.acc.AccBuddyListProp;
import com.aol.acc.AccClientInfo;
import com.aol.acc.AccCustomSession;
import com.aol.acc.AccDirEntry;
import com.aol.acc.AccEvents;
import com.aol.acc.AccException;
import com.aol.acc.AccFileSharingItem;
import com.aol.acc.AccFileSharingSession;
import com.aol.acc.AccFileXfer;
import com.aol.acc.AccFileXferSession;
import com.aol.acc.AccGroup;
import com.aol.acc.AccGroupProp;
import com.aol.acc.AccIm;
import com.aol.acc.AccImInputState;
import com.aol.acc.AccImSession;
import com.aol.acc.AccInstance;
import com.aol.acc.AccInstanceProp;
import com.aol.acc.AccParticipant;
import com.aol.acc.AccParticipantProp;
import com.aol.acc.AccPermissions;
import com.aol.acc.AccPluginInfo;
import com.aol.acc.AccPluginInfoProp;
import com.aol.acc.AccPreferences;
import com.aol.acc.AccRateState;
import com.aol.acc.AccResult;
import com.aol.acc.AccSecondarySession;
import com.aol.acc.AccSecondarySessionState;
import com.aol.acc.AccSession;
import com.aol.acc.AccSessionProp;
import com.aol.acc.AccSessionState;
import com.aol.acc.AccStream;
import com.aol.acc.AccUser;
import com.aol.acc.AccUserProp;
import com.aol.acc.AccVariant;

import configuration.NoEntryException;

public class AIM extends Communication implements AccEvents {
	// Overview: The AIM module is a Communication module that utilizes the AIM
	// service
	// AF(c) = Concrete implementation via an external library's abstraction of
	// a session on the AIM service
	// REP(c) = c.identity != null && c.password != null
	private AccClientInfo accClientInfo = null;
	private AccPreferences accPreferences = null;
	private AccSession accSession = null;

	// The AIM module's identity and password
	private String identity = null;
	private String password = null;

	// Maps communication identities to assigned Publisher modules
	private final HashMap<String, Publisher> publisher = new HashMap<String, Publisher>();

	// REQUIRES: true
	// MODIFIES: this
	// EFFECTS: Constructs a new instance of the AIM module.
	public AIM() {
		// Get the AIM module's identity and password from the configuration
		// module
		try {
			this.identity = AIMPublisher.getConfiguration().getEntry("AIM",
					"AccSession", "identity");
			this.password = AIMPublisher.getConfiguration().getEntry("AIM",
					"AccSession", "password");
		} catch (final NoEntryException e) {
			System.err
					.println("AIM:AccSession: identity and password not configured!");
			System.exit(1);
		}

		// Presumably, we would further initialize the AIM module here. However,
		// when we attempted to separate any of the initialization code from the
		// run method, the AOL Custom Client library promptly threw a hissy fit.
		// So we're leaving it for now.
	}

	@Override
	public void BeforeImReceived(final AccSession arg0,
			final AccImSession arg1, final AccParticipant arg2, final AccIm arg3) {
	}

	@Override
	public void BeforeImSend(final AccSession arg0, final AccImSession arg1,
			final AccParticipant arg2, final AccIm arg3) {
	}

	@Override
	public void OnAlertReceived(final AccSession arg0, final AccAlert arg1) {
	}

	@Override
	public void OnAudioLevelChange(final AccSession arg0,
			final AccAvSession arg1, final String arg2, final int arg3) {
	}

	@Override
	public void OnAvManagerChange(final AccSession arg0,
			final AccAvManager arg1, final AccAvManagerProp arg2,
			final AccResult arg3) {
	}

	@Override
	public void OnAvStreamStateChange(final AccSession arg0,
			final AccAvSession arg1, final String arg2,
			final AccAvStreamType arg3, final AccSecondarySessionState arg4,
			final AccResult arg5) {
	}

	@Override
	public void OnBartItemRequestPropertyResult(final AccSession arg0,
			final AccBartItem arg1, final AccBartItemProp arg2, final int arg3,
			final AccResult arg4, final AccVariant arg5) {
	}

	@Override
	public void OnBuddyAdded(final AccSession arg0, final AccGroup arg1,
			final AccUser arg2, final int arg3, final AccResult arg4) {
	}

	@Override
	public void OnBuddyListChange(final AccSession arg0,
			final AccBuddyList arg1, final AccBuddyListProp arg2) {
	}

	@Override
	public void OnBuddyMoved(final AccSession arg0, final AccUser arg1,
			final AccGroup arg2, final int arg3, final AccGroup arg4,
			final int arg5, final AccResult arg6) {
	}

	@Override
	public void OnBuddyRemoved(final AccSession arg0, final AccGroup arg1,
			final AccUser arg2, final AccResult arg3) {
	}

	@Override
	public void OnChangesBegin(final AccSession arg0) {
	}

	@Override
	public void OnChangesEnd(final AccSession arg0) {
	}

	@Override
	public void OnConfirmAccountResult(final AccSession arg0, final int arg1,
			final AccResult arg2) {
	}

	@Override
	public void OnCustomDataReceived(final AccSession arg0,
			final AccCustomSession arg1, final AccParticipant arg2,
			final AccIm arg3) {
	}

	@Override
	public void OnCustomSendResult(final AccSession arg0,
			final AccCustomSession arg1, final AccParticipant arg2,
			final AccIm arg3, final AccResult arg4) {
	}

	@Override
	public void OnDeleteStoredImsResult(final AccSession arg0, final int arg1,
			final AccResult arg2) {
	}

	@Override
	public void OnDeliverStoredImsResult(final AccSession arg0, final int arg1,
			final AccResult arg2) {
	}

	@Override
	public void OnEjectResult(final AccSession arg0,
			final AccSecondarySession arg1, final String arg2, final int arg3,
			final AccResult arg4) {
	}

	@Override
	public void OnEmbedDownloadComplete(final AccSession arg0,
			final AccImSession arg1, final AccIm arg2) {
	}

	@Override
	public void OnEmbedDownloadProgress(final AccSession arg0,
			final AccImSession arg1, final AccIm arg2, final String arg3,
			final AccStream arg4) {
	}

	@Override
	public void OnEmbedUploadComplete(final AccSession arg0,
			final AccImSession arg1, final AccIm arg2) {
	}

	@Override
	public void OnEmbedUploadProgress(final AccSession arg0,
			final AccImSession arg1, final AccIm arg2, final String arg3,
			final AccStream arg4) {
	}

	@Override
	public void OnFileSharingRequestListingResult(final AccSession arg0,
			final AccFileSharingSession arg1, final AccFileSharingItem arg2,
			final int arg3, final AccResult arg4) {
	}

	@Override
	public void OnFileSharingRequestXferResult(final AccSession arg0,
			final AccFileSharingSession arg1, final AccFileXferSession arg2,
			final int arg3, final AccFileXfer arg4) {
	}

	@Override
	public void OnFileXferCollision(final AccSession arg0,
			final AccFileXferSession arg1, final AccFileXfer arg2) {
	}

	@Override
	public void OnFileXferComplete(final AccSession arg0,
			final AccFileXferSession arg1, final AccFileXfer arg2,
			final AccResult arg3) {
	}

	@Override
	public void OnFileXferProgress(final AccSession arg0,
			final AccFileXferSession arg1, final AccFileXfer arg2) {
	}

	@Override
	public void OnFileXferSessionComplete(final AccSession arg0,
			final AccFileXferSession arg1, final AccResult arg2) {
	}

	@Override
	public void OnGroupAdded(final AccSession arg0, final AccGroup arg1,
			final int arg2, final AccResult arg3) {
	}

	@Override
	public void OnGroupChange(final AccSession arg0, final AccGroup arg1,
			final AccGroupProp arg2) {
	}

	@Override
	public void OnGroupMoved(final AccSession arg0, final AccGroup arg1,
			final int arg2, final int arg3, final AccResult arg4) {
	}

	@Override
	public void OnGroupRemoved(final AccSession arg0, final AccGroup arg1,
			final AccResult arg2) {
	}

	@Override
	public void OnIdleStateChange(final AccSession arg0, final int arg1) {
	}

	@Override
	// REQUIRES: valid state given parameters (handled by external library)
	// MODIFIES: this.publisher
	// EFFECTS: Handles instant messages first by assigning the specified
	// Publisher module to the given communication identity then by passing all
	// communication messages through to the Publisher module. Unassigns the
	// Publisher module when the Publisher module's state is 0.
	public void OnImReceived(final AccSession arg0, final AccImSession arg1,
			final AccParticipant arg2, final AccIm arg3) {
		String identity = null; // The communication identity
		String message = null; // The communication message

		try {
			// Add the communication module's prefix to the communication
			// identity. This prevents configuration conflict between
			// communication modules' communication identities
			identity = "AIM:" + arg2.getName();
			// Get the unformatted text of the communication message
			message = arg3.getConvertedText("text/plain");
		} catch (final AccException e) {
			// If for some reason there is an exception getting either of these
			// variables, print the stack trace and bail.
			e.printStackTrace();
			return;
		}

		// Assign the null Publisher module initially
		if (!this.publisher.containsKey(identity)) {
			this.publisher.put(identity, null);
		}

		// Assign the specified Publisher module
		if (this.publisher.get(identity) == null) {
			Class publisherClass = null; // The raw type of the module's class
			boolean invalidClass = true; // Set to false when REQUIRES are met

			try {
				// REQUIRES: The module's class must exist
				publisherClass = ClassLoader.getSystemClassLoader().loadClass(
						"publisher." + message);
				invalidClass = false;
			} catch (final Exception e) {
				// Assume that the class does not exist, or that there was some
				// other generic exception.
				invalidClass = true;
			} finally {
				// REQUIRES: The module's class must be a subclass of Publisher
				if (publisherClass != null) {
					if (!publisherClass.getSuperclass().getName().equals(
							"publisher.Publisher")) {
						invalidClass = true;
					}
				}
			}

			// If REQUIRES are not met, send an error response and return
			if (invalidClass) {
				try {
					arg1
							.sendIm(this.accSession.createIm(
									"Please specify a Publisher module.",
									"text/plain"));
				} catch (final AccException e) {
					// If for some reason there is an exception sending the
					// response, print a stack trace.
					e.printStackTrace();
				}
				return;
			}

			// Instantiate the Publisher module
			try {
				// Use the default constructor to construct a new instance
				final Publisher publisherObject = (Publisher) publisherClass
						.newInstance();
				// Set the Publisher module's communication identity
				publisherObject.setIdentity(identity);
				// Assign the specified Publisher module
				this.publisher.put(identity, publisherObject);
			} catch (final InstantiationException e) {
				// If there is an exception constructing a new instance, print
				// the stack trace and bail.
				e.printStackTrace();
				return;
			} catch (final IllegalAccessException e) {
				// If there is an exception constructing a new instance, print
				// the stack trace and bail.
				e.printStackTrace();
				return;
			}
		}

		// Get the assigned Publisher module
		final Publisher publisherObject = this.publisher.get(identity);
		// Pass all communication messages through to the Publisher module
		final String publisherResponses = publisherObject.run(message);

		// Send the Publisher response(s), if any
		if (publisherResponses != null) {
			try {
				// Split the Publisher response(s)
				final String[] responseArray = publisherResponses.split("\r\n");
				for (final String publisherResponse : responseArray) {
					arg1.sendIm(this.accSession.createIm(publisherResponse,
							"text/plain"));
				}
			} catch (final AccException e) {
				// If for some reason there is an exception sending the
				// response, print a stack trace and bail.
				e.printStackTrace();
				return;
			}
		}

		// Unassign the Publisher module when the Publisher module's state is 0.
		if (publisherObject.getState() == 0) {
			this.publisher.put(identity, null);
		}
	}

	@Override
	public void OnImSendResult(final AccSession arg0, final AccImSession arg1,
			final AccParticipant arg2, final AccIm arg3, final AccResult arg4) {
	}

	@Override
	public void OnImSent(final AccSession arg0, final AccImSession arg1,
			final AccParticipant arg2, final AccIm arg3) {
	}

	@Override
	public void OnInputStateChange(final AccSession arg0,
			final AccImSession arg1, final String arg2,
			final AccImInputState arg3) {
	}

	@Override
	public void OnInstanceChange(final AccSession arg0, final AccInstance arg1,
			final AccInstance arg2, final AccInstanceProp arg3) {
	}

	@Override
	public void OnInviteResult(final AccSession arg0,
			final AccSecondarySession arg1, final String arg2, final int arg3,
			final AccResult arg4) {
	}

	@Override
	public void OnLocalImReceived(final AccSession arg0,
			final AccImSession arg1, final AccIm arg2) {
	}

	@Override
	public void OnLookupUsersResult(final AccSession arg0, final String[] arg1,
			final int arg2, final AccResult arg3, final AccUser[] arg4) {
	}

	@Override
	public void OnNewFileXfer(final AccSession arg0,
			final AccFileXferSession arg1, final AccFileXfer arg2) {
	}

	@Override
	public void OnNewSecondarySession(final AccSession arg0,
			final AccSecondarySession arg1, final int arg2) {
	}

	@Override
	public void OnParticipantChange(final AccSession arg0,
			final AccSecondarySession arg1, final AccParticipant arg2,
			final AccParticipant arg3, final AccParticipantProp arg4) {
	}

	@Override
	public void OnParticipantJoined(final AccSession arg0,
			final AccSecondarySession arg1, final AccParticipant arg2) {
	}

	@Override
	public void OnParticipantLeft(final AccSession arg0,
			final AccSecondarySession arg1, final AccParticipant arg2,
			final AccResult arg3, final String arg4, final String arg5) {
	}

	@Override
	public void OnPluginChange(final AccSession arg0, final AccPluginInfo arg1,
			final AccPluginInfoProp arg2) {
	}

	@Override
	public void OnPluginUninstall(final AccSession arg0,
			final AccPluginInfo arg1) {
	}

	@Override
	public void OnPreferenceChange(final AccSession arg0, final String arg1,
			final AccResult arg2) {
	}

	@Override
	public void OnPreferenceInvalid(final AccSession arg0, final String arg1,
			final AccResult arg2) {
	}

	@Override
	public void OnPreferenceResult(final AccSession arg0, final String arg1,
			final int arg2, final String arg3, final AccResult arg4) {
	}

	@Override
	public void OnPushBuddyFeedResult(final AccSession arg0, final int arg1,
			final AccResult arg2, final String arg3) {
	}

	@Override
	public void OnRateLimitStateChange(final AccSession arg0,
			final AccImSession arg1, final AccRateState arg2) {
	}

	@Override
	public void OnReportUserResult(final AccSession arg0, final AccUser arg1,
			final int arg2, final AccResult arg3, final int arg4, final int arg5) {
	}

	@Override
	public void OnRequestServiceResult(final AccSession arg0, final int arg1,
			final AccResult arg2, final String arg3, final int arg4,
			final AccVariant arg5) {
	}

	@Override
	public void OnRequestSummariesResult(final AccSession arg0, final int arg1,
			final AccResult arg2, final AccVariant arg3) {
	}

	@Override
	public void OnSearchDirectoryResult(final AccSession arg0, final int arg1,
			final AccResult arg2, final AccDirEntry arg3) {
	}

	@Override
	public void OnSecondarySessionChange(final AccSession arg0,
			final AccSecondarySession arg1, final int arg2) {
	}

	@Override
	public void OnSecondarySessionStateChange(final AccSession arg0,
			final AccSecondarySession arg1,
			final AccSecondarySessionState arg2, final AccResult arg3) {
	}

	@Override
	public void OnSendInviteMailResult(final AccSession arg0, final int arg1,
			final AccResult arg2) {
	}

	@Override
	public void OnSessionChange(final AccSession arg0, final AccSessionProp arg1) {
	}

	@Override
	public void OnSoundEffectReceived(final AccSession arg0,
			final AccAvSession arg1, final String arg2, final String arg3) {
	}

	@Override
	public void OnStateChange(final AccSession arg0,
			final AccSessionState arg1, final AccResult arg2) {
	}

	@Override
	public void OnUserChange(final AccSession arg0, final AccUser arg1,
			final AccUser arg2, final AccUserProp arg3, final AccResult arg4) {
	}

	@Override
	public void OnUserRequestPropertyResult(final AccSession arg0,
			final AccUser arg1, final AccUserProp arg2, final int arg3,
			final AccResult arg4, final AccVariant arg5) {
	}

	@Override
	// REQUIRES: this.identity != null && this.password != null
	// MODIFIES: this.accClientInfo, this.accPreferences, this.accSession
	// EFFECTS: Further initializes the AIM module and connects to the AIM
	// service, routinely pumping messages from the service to the event
	// listener.
	public void run() {
		try {
			this.accSession = new AccSession();

			// Get the client info object which must be set up prior to signon.
			this.accClientInfo = this.accSession.getClientInfo();

			// Javadoc: The client description; format should be
			// "Name/Version (key="MyKey")"
			// However, this is the format that was in the example code; the
			// example that was in the Javadoc does not work. Clever, that.
			this.accClientInfo
					.setDescription("AIMPublisher (Key:mi1MQtY6-K-sojGs)");

			// Set the application's preferences hook, which should be set up
			// prior to signon.
			this.accSession.setPrefsHook(new AIMPrefsHook());

			// Return the preferences object. This object is used for getting
			// and setting preference values.
			this.accPreferences = this.accSession.getPrefs();

			this.accPreferences.setValue("aimcc.im.chat.permissions.buddies",
					AccPermissions.RejectAll);
			this.accPreferences.setValue(
					"aimcc.im.chat.permissions.nonBuddies",
					AccPermissions.RejectAll);
			this.accPreferences.setValue("aimcc.im.direct.permissions.buddies",
					AccPermissions.RejectAll);
			this.accPreferences.setValue(
					"aimcc.im.direct.permissions.nonBuddies",
					AccPermissions.RejectAll);
			this.accPreferences.setValue(
					"aimcc.im.standard.permissions.buddies",
					AccPermissions.AcceptAll);
			this.accPreferences.setValue(
					"aimcc.im.standard.permissions.nonBuddies",
					AccPermissions.AcceptAll);

			this.accSession.setEventListener(this);

			// Sets the identity for a session, which must be done prior to
			// signon.
			this.accSession.setIdentity(this.identity);

			// Signon the current user to the AIM service.
			this.accSession.signOn(this.password);
		} catch (final AccException e) {
			// If for some reason there is an exception signing on to the AIM
			// service, print a stack trace and bail.
			e.printStackTrace();
			return;
		}

		// Routinely pump messages from the service to the event listener
		while (true) {
			AccSession.pump(50);
			try {
				Thread.sleep(50);
			} catch (final InterruptedException e) {
				try {
					this.accSession.signOff();
				} catch (final AccException e1) {
					// If for some reason there is an exception signing off of
					// the AIM service, print a stack trace.
					e.printStackTrace();
				}
				return;
			}
		}
	}
}